/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1988 by Sun Microsystems, Inc.
 */

#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/kbd.h>
#include <sys/kbio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

typedef enum {
	SM_INVALID,	/* this shift mask is invalid for this keyboard */
	SM_NORMAL,	/* "normal", valid shift mask */
	SM_NUMLOCK,	/* "Num Lock" shift mask */
	SM_UP		/* "Up" shift mask */
} smtype_t;

typedef struct {
	char	*sm_name;
	int	sm_mask;
	smtype_t sm_type;
} smentry_t;


smentry_t shiftmasks[] = {
	{ "base",	0,		SM_NORMAL },
	{ "shift",	SHIFTMASK,	SM_NORMAL },
	{ "caps",	CAPSMASK,	SM_NORMAL },
	{ "ctrl",	CTRLMASK,	SM_NORMAL },
	{ "altg",	ALTGRAPHMASK,	SM_NORMAL },
	{ "numl",	NUMLOCKMASK,	SM_NUMLOCK },
	{ "up",		UPMASK,		SM_UP },
};

#define	NSHIFTS	(sizeof (shiftmasks) / sizeof (shiftmasks[0]))

static void	printentry(struct kiockeymap *kio);
static void	printchar(int character, int delim);

/*ARGSUSED*/
int
main(int argc, char **argv)
{
	register int kbdfd;
	register int keystation;
	register int shift;
	int ktype;
	struct kiockeymap keyentry[NSHIFTS];
	register int allsame;

	if ((kbdfd = open("/dev/kbd", O_WRONLY)) < 0) {
		perror("dumpkeys: /dev/kbd");
		return (1);
	}
	if (ioctl(kbdfd, KIOCTYPE, &ktype) < 0) {
		perror("dumpkeys: ioctl(KIOCTYPE)");
		return (1);
	}
	/* if no keyboard detected, or ascii terminal, exit silently */
	if (ktype == KB_ASCII || ktype < 0)
		exit(0);

	/*
	 * See which shift masks are valid for this keyboard.
	 * We do that by trying to get the entry for keystation 0 and that
	 * shift mask; if the "ioctl" fails, we assume it's because the shift
	 * mask is invalid.
	 */
	for (shift = 0; shift < NSHIFTS; shift++) {
		keyentry[shift].kio_tablemask =
		    shiftmasks[shift].sm_mask;
		keyentry[shift].kio_station = 0;
		if (ioctl(kbdfd, KIOCGKEY, &keyentry[shift]) < 0)
			shiftmasks[shift].sm_type = SM_INVALID;
	}

	/*
	 * Loop until we get an EINVAL, so we don't have to know
	 * how big the table might be.
	 */
	for (keystation = 0; ; keystation++) {
		for (shift = 0; shift < NSHIFTS; shift++) {
			if (shiftmasks[shift].sm_type != SM_INVALID) {
				keyentry[shift].kio_tablemask =
				    shiftmasks[shift].sm_mask;
				keyentry[shift].kio_station = keystation;
				if (ioctl(kbdfd, KIOCGKEY,
				    &keyentry[shift]) < 0) {
					if (errno == EINVAL)
						return (0);
					perror("dumpkeys: KIOCGKEY");
					return (1);
				}
			}
		}

		(void) printf("key %d\t", keystation);

		/*
		 * See if all the "normal" entries (all but the Num Lock and Up
		 * entries) are the same.
		 */
		allsame = 1;
		for (shift = 1; shift < NSHIFTS; shift++) {
			if (shiftmasks[shift].sm_type == SM_NORMAL) {
				if (keyentry[0].kio_entry
				    != keyentry[shift].kio_entry) {
					allsame = 0;
					break;
				}
			}
		}

		if (allsame) {
			/*
			 * All of the "normal" entries are the same; just print
			 * "all".
			 */
			(void) printf(" all ");
			printentry(&keyentry[0]);
		} else {
			/*
			 * The normal entries aren't all the same; print them
			 * individually.
			 */
			for (shift = 0; shift < NSHIFTS; shift++) {
				if (shiftmasks[shift].sm_type == SM_NORMAL) {
					(void) printf(" %s ",
					    shiftmasks[shift].sm_name);
					printentry(&keyentry[shift]);
				}
			}
		}
		if (allsame && keyentry[0].kio_entry == HOLE) {
			/*
			 * This key is a "hole"; if either the Num Lock or Up
			 * entry isn't a "hole", print it.
			 */
			for (shift = 0; shift < NSHIFTS; shift++) {
				switch (shiftmasks[shift].sm_type) {

				case SM_NUMLOCK:
				case SM_UP:
					if (keyentry[shift].kio_entry
					    != HOLE) {
						(void) printf(" %s ",
						    shiftmasks[shift].sm_name);
						printentry(&keyentry[shift]);
					}
					break;

				default:
					break;
				}
			}
		} else {
			/*
			 * This entry isn't a "hole"; if the Num Lock entry
			 * isn't NONL (i.e, if Num Lock actually does
			 * something) print it, and if the Up entry isn't NOP
			 * (i.e., if up transitions on this key actually do
			 * something) print it.
			 */
			for (shift = 0; shift < NSHIFTS; shift++) {
				switch (shiftmasks[shift].sm_type) {

				case SM_NUMLOCK:
					if (keyentry[shift].kio_entry
					    != NONL) {
						(void) printf(" %s ",
						    shiftmasks[shift].sm_name);
						printentry(&keyentry[shift]);
					}
					break;

				case SM_UP:
					if (keyentry[shift].kio_entry
					    != NOP) {
						(void) printf(" %s ",
						    shiftmasks[shift].sm_name);
						printentry(&keyentry[shift]);
					}
					break;

				default:
					break;
				}
			}
		}
		(void) printf("\n");
	}
}

static char *shiftkeys[] = {
	"capslock",
	"shiftlock",
	"leftshift",
	"rightshift",
	"leftctrl",
	"rightctrl",
	"meta",			/* not used */
	"top",			/* not used */
	"cmd",			/* reserved */
	"altgraph",
	"alt",
	"numlock",
};

#define	NSHIFTKEYS	(sizeof (shiftkeys) / sizeof (shiftkeys[0]))

static char *buckybits[] = {
	"metabit",
	"systembit",
};

#define	NBUCKYBITS	(sizeof (buckybits) / sizeof (buckybits[0]))

static char *funnies[] = {
	"nop",
	"oops",
	"hole",
	"",			/* not used */
	"",			/* not used */
	"",			/* not used */
	"reset",
	"error",
	"idle",
	"compose",
	"nonl",
};

#define	NFUNNIES	(sizeof (funnies) / sizeof (funnies[0]))

static char *fa_class[] = {
	"fa_umlaut",
	"fa_cflex",
	"fa_tilde",
	"fa_cedilla",
	"fa_acute",
	"fa_grave",
	"fa_macron",
	"fa_breve",
	"fa_dot",
	"fa_slash",
	"fa_ring",
	"fa_apostrophe",
	"fa_dacute",
	"fa_ogonek",
	"fa_caron"
};

#define	NFA_CLASS	(sizeof (fa_class) / sizeof (fa_class[0]))

typedef struct {
	char	*string;
	char	*name;
} builtin_string_t;

builtin_string_t builtin_strings[] = {
	{ "\033[H",	"homearrow" },
	{ "\033[A",	"uparrow" },
	{ "\033[B",	"downarrow" },
	{ "\033[D",	"leftarrow" },
	{ "\033[C",	"rightarrow" },
};

#define	NBUILTIN_STRINGS	(sizeof (builtin_strings) / \
					sizeof (builtin_strings[0]))

static char	*fkeysets[] = {
	"lf",
	"rf",
	"tf",
	"bf",
};

#define	NFKEYSETS	(sizeof (fkeysets) / sizeof (fkeysets[0]))

static char	*padkeys[] = {
	"padequal",
	"padslash",
	"padstar",
	"padminus",
	"padsep",
	"pad7",
	"pad8",
	"pad9",
	"padplus",
	"pad4",
	"pad5",
	"pad6",
	"pad1",
	"pad2",
	"pad3",
	"pad0",
	"paddot",
	"padenter",
};

#define	NPADKEYS	(sizeof (padkeys) / sizeof (padkeys[0]))

static void
printentry(struct kiockeymap *kio)
{
	int entry = (kio->kio_entry & 0x1F);
	int fkeyset;
	int i;
	int c;

	switch (KEYFLAGS(kio->kio_entry)) {

	case 0x0:
		if (kio->kio_entry == '"')
			(void) printf("'\"'");	/* special case */
		else if (kio->kio_entry == ' ')
			(void) printf("' '");	/* special case */
		else
			printchar((int)kio->kio_entry, '\'');
		break;

	case SHIFTKEYS:
		if (entry < NSHIFTKEYS)
			(void) printf("shiftkeys+%s", shiftkeys[entry]);
		else
			(void) printf("%#4x", kio->kio_entry);
		break;

	case BUCKYBITS:
		if (entry < NBUCKYBITS)
			(void) printf("buckybits+%s", buckybits[entry]);
		else
			(void) printf("%#4x", kio->kio_entry);
		break;

	case FUNNY:
		if (entry < NFUNNIES)
			(void) printf("%s", funnies[entry]);
		else
			(void) printf("%#4x", kio->kio_entry);
		break;

	case FA_CLASS:
		if (entry < NFA_CLASS)
			(void) printf("%s", fa_class[entry]);
		else
			(void) printf("%#4x", kio->kio_entry);
		break;

	case STRING:
		if (entry < NBUILTIN_STRINGS && strncmp(kio->kio_string,
		    builtin_strings[entry].string, KTAB_STRLEN) == 0)
			(void) printf("string+%s", builtin_strings[entry].name);
		else {
			(void) printf("\"");
			for (i = 0;
			    i < KTAB_STRLEN && (c = kio->kio_string[i]) != '\0';
			    i++)
				printchar(c, '"');
			(void) printf("\"");
		}
		break;

	case FUNCKEYS:
		fkeyset = (int)(kio->kio_entry & 0xF0) >> 4;
		if (fkeyset < NFKEYSETS)
			(void) printf("%s(%d)", fkeysets[fkeyset],
			    (entry & 0x0F) + 1);
		else
			(void) printf("%#4x", kio->kio_entry);
		break;

	case PADKEYS:
		if (entry < NPADKEYS)
			(void) printf("%s", padkeys[entry]);
		else
			(void) printf("%#4x", kio->kio_entry);
		break;

	default:
		(void) printf("%#4x", kio->kio_entry);
		break;
	}
}

static void
printchar(int character, int delim)
{
	switch (character) {

	case '\n':
		(void) printf("'\\n'");
		break;

	case '\t':
		(void) printf("'\\t'");
		break;

	case '\b':
		(void) printf("'\\b'");
		break;

	case '\r':
		(void) printf("'\\r'");
		break;

	case '\v':
		(void) printf("'\\v'");
		break;

	case '\\':
		(void) printf("'\\\\'");
		break;

	default:
		if (isprint(character)) {
			if (character == delim)
				(void) printf("'\\'");
			(void) printf("%c", character);
		} else {
			if (character < 040)
				(void) printf("^%c", character + 0100);
			else if (character <= 0xff)
				(void) printf("'\\%.3o'", character);
			else
				(void) printf("%#4x", character);
		}
		break;
	}
}
